﻿using HP.UtilsLib;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Windows.Foundation;
using Windows.Media.Audio;
using Windows.Media.Render;
using Windows.Storage;
using Windows.Storage.Streams;
using Xiaowei.Diagnostics;
using Xiaowei.Models;
using Debug = Xiaowei.Diagnostics.Debug;

namespace Xiaowei.Services
{
    //implement this player with audiograph
    public class TTSPlayerWithAudioGraph : ITTSPlayer
    {
        private AudioGraph graph;
        private AudioFileInputNode fileInput;
        private AudioDeviceOutputNode outputNode;
        public event Action<bool> IsPlayingChanged;

        Stopwatch sw = new Stopwatch();
        //wair for Play method ending
        private bool isPlaying = false;

        //sometimes, auidograph will stop to work.For exiting under block
        //timer is set.
        private readonly FailsafeTimer failsafeTimer = new FailsafeTimer();

        public bool IsPlaying
        {
            get
            {
                return isPlaying;
            }
            private set
            {
                if (isPlaying != value)
                {
                    isPlaying = value;
                    IsPlayingChanged?.Invoke(value);
                }
            }
        }

        public TTSPlayerWithAudioGraph()
        {
            failsafeTimer.FailsafeTimeout += () =>
            {
                Debug.WriteLine($"TTSPlayerWithAudioGraph Timeout");
                //fileInput.Stop();
                sw.Stop();
                Debug.WriteLine($"TTSPlayerWithAudioGraph Timeout end");
            };
        }

        private object locker = new object();
        public bool Play(string url)
        {
            lock (locker)
            {
                if (IsPlaying)
                {
                    Cancel();
                }
                var wait = TaskAwaiterHelper.GetTaskAwaiter(
                    async () =>
                    {
                        return await PlayCore(url).ConfigureAwait(false);
                    });
                var result = wait.GetResult();
                wait.Dispose();
                return result;
            }
        }

        private async Task<bool> PlayCore(string url)
        {
            Debug.WriteLine($"TTSPlayerWithAudioGraph:Play begin,URL{url}");
            sw.Restart();
            failsafeTimer.Start(15000);
            Dispose();

            StorageFile file = null;
            if (url.StartsWith("ms-appx:"))
            {
                //local tts
                Debug.WriteLine($"TTSPlayerWithAudioGraph.Play load local tts");
                file = await StorageFile.GetFileFromApplicationUriAsync(new Uri(url));
                Debug.WriteLine($"TTSPlayerWithAudioGraph.Play load local tts end");
            }
            else
            {
                Debug.WriteLine($"TTSPlayerWithAudioGraph.Play load remote tts");
                //remote tts
                var uri = new Uri(url);
                var reference = RandomAccessStreamReference.CreateFromUri(uri);
                file = await StorageFile.CreateStreamedFileFromUriAsync(".mp3", uri, reference);
                Debug.WriteLine($"TTSPlayerWithAudioGraph.Play load remote tts end");
            }


            if (file != null && file.IsAvailable)
            {
                //sometimes, audio device will suddenly stop to work， which
                //cause play crash. So the try is added.
                try
                {
                    Debug.WriteLine($"TTSPlayerWithAudioGraph.Play:url:{url}, IsAvailable:{file.IsAvailable}, FileType:{file.FileType}, ContentType:{file.ContentType}, elapsed:{sw.ElapsedMilliseconds} ms");
                    var ok = await CreateAudioGraphAsync(file).ConfigureAwait(false);
                    Debug.WriteLine($"TTSPlayerWithAudioGraph.CreateAudioGraphAsync:{sw.ElapsedMilliseconds} ms,url:{url}");
                    if (!ok)
                    {
                        Dispose();
                        Debug.WriteLine($"TTSPlayerWithAudioGraph:Play end false,URL{url}");
                        IsPlaying = false;
                        failsafeTimer.Stop();
                        return false;
                    }
                    else
                    {
                        fileInput.FileCompleted += (inode, o) =>
                        {
                            lock (this)
                            {
                                Debug.WriteLine($"TTSPlayerWithAudioGraph.fileInput.FileCompleted,URL{url}");
                                IsPlaying = false;
                                Dispose();
                                Debug.WriteLine("TTSPlayerWithAudioGraph calling MediaEnded?.Invoke()");
                                Debug.WriteLine($"TTSPlayerWithAudioGraph.fileInput.FileCompleted end,URL:{url}");
                            }

                        };
                        Debug.WriteLine($"TTSPlayerWithAudioGraph.Before graph.Start:{sw.ElapsedMilliseconds} ms");
                        graph.Start();
                        Debug.WriteLine($"TTSPlayerWithAudioGraph.After graph.Start:{sw.ElapsedMilliseconds} ms");
                        sw.Stop();
                        Debug.WriteLine($"TTSPlayerWithAudioGraph:Play end true,URL{url}");
                        IsPlaying = true;
                        failsafeTimer.Stop();
                        //MediaPlaying?.Invoke();
                        return true;
                    }
                }
                catch (Exception ex)
                {
                    Debug.WriteLine($"TTSPlayerWithAudioGraph:Play end false3,URL{url}");
                    Dispose();
                    IsPlaying = false;
                    failsafeTimer.Stop();
                    return false;
                }

            }
            else
            {
                Debug.WriteLine($"TTSPlayerWithAudioGraph:Play end false2,URL{url}");
                IsPlaying = false;
                failsafeTimer.Stop();
                return false;
            }
        }

        public void Cancel()
        {
            lock (this)
            {
                Debug.WriteLine("TTSPlayerWithAudioGraph.Cancel");
                if (fileInput != null)
                {
                    fileInput.Stop();
                }

                Debug.WriteLine("TTSPlayerWithAudioGraph.Cancel enter lock");
                Dispose();
                if (IsPlaying)
                {
                    Debug.WriteLine("TTSPlayerWithAudioGraph calling MediaEnded?.Invoke()1");
                }
                else
                {
                    Debug.WriteLine("TTSPlayerWithAudioGraph.Cancel failed");
                }

                Debug.WriteLine("TTSPlayerWithAudioGraph.Cancel end");
            }
        }

        /*
        private async void OnFileCompleted(IAsyncOperation<StorageFile> asyncInfo, AsyncStatus asyncStatus)
        {
            var file = asyncInfo.GetResults();
            Debug.WriteLine($"OnFileCompleted file:IsAvailable:{file.IsAvailable}, " +
                        $"FileType:{file.FileType}, ContentType:{file.ContentType}, Attributes:{file.Attributes}");
            var ok = await CreateAudioGraphAsync(file).ConfigureAwait(false);
            if (!ok)
            {
                Dispose();
            }
            else
            {
                fileInput.FileCompleted += (inode, o) =>
                {
                    Dispose();
                    MediaEnded?.Invoke();
                };
                graph.Start();
                MediaPlaying?.Invoke();
                sw.Stop();
                Debug.WriteLine($"Start to play tts:{sw.ElapsedMilliseconds}ms");
            }
        }
        */

        private async Task<bool> CreateAudioGraphAsync(StorageFile file)
        {
            Debug.WriteLine($"graph, begin");
            Stopwatch sw = new Stopwatch();
            sw.Start();
            // Create an AudioGraph with default settings
            var settings = new AudioGraphSettings(AudioRenderCategory.Media);
            var result = await AudioGraph.CreateAsync(settings);
            if (result.Status != AudioGraphCreationStatus.Success)
            {
                Debug.WriteLine($"graph, end false1");
                return false;
            }

            graph = result.Graph;
            Debug.WriteLine($"graph, elapse:{sw.ElapsedMilliseconds} ms");
            // Create a device output node
            var ouputNodeResult = await graph.CreateDeviceOutputNodeAsync();

            if (ouputNodeResult.Status != AudioDeviceNodeCreationStatus.Success)
            {
                Debug.WriteLine($"graph, end false2");
                return false;
            }

            outputNode = ouputNodeResult.DeviceOutputNode;
            if (outputNode == null) return false;
            Debug.WriteLine($"outputNode, elapse:{sw.ElapsedMilliseconds} ms");
            //create file input node
            var fileInputResult = await graph.CreateFileInputNodeAsync(file);
            if (AudioFileNodeCreationStatus.Success != fileInputResult.Status)
            {
                Debug.WriteLine($"graph, end false3");
                return false;
            }

            fileInput = fileInputResult.FileInputNode;
            Debug.WriteLine($"fileInput, elapse:{sw.ElapsedMilliseconds} ms");
            //fileInput.FileCompleted += (f, o) =>
            //{
            //};

            fileInput.AddOutgoingConnection(outputNode);
            fileInput.StartTime = TimeSpan.FromSeconds(0);
            sw.Stop();
            Debug.WriteLine($"AddOutgoingConnection, elapse:{sw.ElapsedMilliseconds} ms");
            Debug.WriteLine($"graph, end");
            return true;
        }

        private void Dispose()
        {
            Debug.WriteLine("TTSPlayerWithAudioGraph.Dispose");
            if (fileInput != null)
            {
                fileInput.Dispose();
                fileInput = null;
            }

            if(outputNode != null)
            {
                outputNode.Dispose();
                outputNode = null;
            }

            if (graph != null)
            {
                graph.Dispose();
                graph = null;
            }
            Debug.WriteLine("TTSPlayerWithAudioGraph.Dispose end");
        }
    }
}
